package compilers.AST.statement;

import java.io.PrintStream;
import java.util.Iterator;

import compilers.AST.Node;
import compilers.AST.basics.Identifier;
import compilers.AST.expression.Expression;
import compilers.AST.types.MyType;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTable;
import compilers.symbolTable.SymbolTableElement;
import compilers.symbolTable.SymbolTableScope;
import compilers.symbolTable.SymbolTableVariableElement;

public class VariableDeclarationStatement extends Statement {

	private TypeSpecifier fType;
	private Identifier fIdentifier;
	private boolean fConstant;
	private Expression fRValue;
	
	public VariableDeclarationStatement() {
		fConstant = false;
		fRValue = null;
	}
	
	public VariableDeclarationStatement(int lineNr) {
		super(lineNr);
		fConstant = false;
		fRValue = null;
	}
	
	public void print(int identLevel) {
		println(identLevel, "VariableDeclarationStatement");
		fType.print(identLevel+1);
		if (fConstant) {
			println(identLevel+1, "Constant");
		}
		fIdentifier.print(identLevel+1);
		if (fRValue != null) {
			fRValue.print(identLevel+1);
		}
 	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		fType.checkSemantics(currentScope);
		// Void and bool types are not allowed for variable declarations
		if (fType.getType(currentScope) == TypeSpecifier.VOID) {
			throw new SemanticalException("Variable declaration of type void is not allowed.", getLine());
		} else if (fType.getType(currentScope) == TypeSpecifier.BOOLEAN) {
			throw new SemanticalException("Variable declaration of type boolean is not allowed.", getLine());
		}
		
		// Check type consistency when assignment is present
		if (fRValue != null) {
			fRValue.checkSemantics(currentScope);
			
			// Check whether rValue is not an array
			if (fRValue.isArray(currentScope)) {
				throw new SemanticalException("Assignment of array to variable is not allowed.", getLine());
			}
			
			// Int maybe assigned to float
			TypeSpecifier varType = new TypeSpecifier(fIdentifier.getResultType(currentScope));
			TypeSpecifier exprType = new TypeSpecifier(fRValue.getResultType(currentScope));
			if (varType.getType(currentScope) == TypeSpecifier.FLOAT) {
				if ( (exprType.getType(currentScope) != TypeSpecifier.FLOAT) && (exprType.getType(currentScope) != TypeSpecifier.INTEGER)) {
					throw new SemanticalException("Assignment of type " + exprType.getTypeName() + " to variable of type " + varType.getTypeName() + " is not allowed.", getLine());
				}
			} else {
				if(varType.getType(currentScope) != exprType.getType(currentScope)) {
					throw new SemanticalException("Assignment of type " + exprType.getTypeName() + " to variable of type " + varType.getTypeName() + " is not allowed.", getLine());
				}
			}
			
			// Update symbol table value if constant declaration of constant expression
			if (fConstant) {
				if (fRValue.isConstant(currentScope)) {
					MyType value = fRValue.calculateValue(currentScope);
					SymbolTableVariableElement element = (SymbolTableVariableElement) currentScope.getElement(fIdentifier.getValue(), fNodeId);
					element.setConstantValue(value);
				}
			}
		}
	}
	
	public void setType(TypeSpecifier type) {
		fType = type;
	}
	
	public void setIdentifier(Identifier ident) {
		fIdentifier = ident;
	}
	
	public void makeConstant() {
		fConstant = true;
	}
	
	public void setRValue(Expression rValue) {
		fRValue = rValue;
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {
		SymbolTableVariableElement element = (SymbolTableVariableElement) currentScope.getElement(fIdentifier.getValue(), fNodeId);
		if(this.fRValue == null) {
			if (fType.getType(currentScope) == TypeSpecifier.FLOAT) {
				pGen.load('r', "0.0");
			} else {
				pGen.load(fType.getPType(currentScope), "0");
			}
			pGen.str(fType.getPType(currentScope), currentScope.getDepth(), element.getMemoryAddress());
		} else {
			if (fRValue.isConstant(currentScope)) {
				//If the value has to be cast to another type, we have to do this in advance
				String value = "";
				if(this.fRValue.getOriginalType(currentScope) != this.fRValue.getResultType(currentScope)) {
					value = fRValue.calculateValue(currentScope).toType(this.fRValue.getResultType(currentScope));
				} else if (fType.getType(currentScope) != fRValue.getResultType(currentScope)) {
					value = fRValue.calculateValue(currentScope).toType(fType.getType(currentScope));
				} else {
					value = fRValue.calculateValue(currentScope).valueToString();
				}
				pGen.load(fType.getPType(currentScope), value);
				
			} else {
				fRValue.generatePcode(pGen, currentScope);
				if (fType.getType(currentScope) != fRValue.getResultType(currentScope)) {
					pGen.conv('i', 'r');
				}
			}
			pGen.str(element.getType().getPType(currentScope), currentScope.getDepth(), element.getMemoryAddress());
		}
	}
}
